#include "ringbuffer.h"
#include <stdlib.h>
#include <string.h>

static ReadCallback global_read_callback = NULL;

MemoryHandle *init_circular_buffer(uint32_t size)
{
  MemoryHandle *handle = malloc(sizeof(MemoryHandle));
  if (handle == NULL) {
    return NULL;
  }

  uint8_t *buffer = malloc(size);
  if (buffer == NULL) {
    free(handle);
    return NULL;
  }
  memset(buffer, 0, size);

  handle->data = buffer;
  handle->capacity = size;
  handle->length = 0;
  handle->position = 0;
  handle->callback = NULL;

  return handle;
}

void uninit_circular_buffer(MemoryHandle *handle)
{
  if (handle == NULL) {
    return;
  }
  free(handle->data);
  free(handle);
}

void circular_buffer_read_callback(MemoryHandle *handle, ReadCallback callback)
{
  handle->callback = callback;
}

void remove_circular_buffer_read_callback(MemoryHandle *handle)
{
  handle->callback = NULL;
}

uint32_t read_from_circular_buffer(void *buffer, uint32_t size, uint32_t count, void *handle_ptr)
{
  CircularBufferContext *context = (CircularBufferContext *)handle_ptr;
  uint8_t *dest_buffer = (uint8_t *)buffer;
  uint32_t required_bytes = size * count;
  uint32_t read_bytes;
  uint32_t remaining_bytes;
  uint32_t write_pos = context->write_pos;

  if (required_bytes == 0) {
    return 0;
  }

  if (write_pos >= context->read_pos) {
    remaining_bytes = write_pos - context->read_pos;

    if (required_bytes > remaining_bytes) {
      read_bytes = remaining_bytes;
      memcpy(dest_buffer, &context->data[context->read_pos], read_bytes);
      context->read_pos += read_bytes;

      if (global_read_callback) {
        read_bytes += global_read_callback(dest_buffer + read_bytes, required_bytes - read_bytes);
      }
    } else {
      read_bytes = required_bytes;
      memcpy(dest_buffer, &context->data[context->read_pos], read_bytes);
      context->read_pos += read_bytes;
    }
  } else {
    remaining_bytes = context->capacity - context->read_pos;

    if (required_bytes > remaining_bytes) {
        read_bytes = remaining_bytes;
        memcpy(dest_buffer, &context->data[context->read_pos], read_bytes);
        if (required_bytes - read_bytes > write_pos) {
          memcpy(dest_buffer + read_bytes, &context->data[0], write_pos);
          context->read_pos = write_pos;
          read_bytes += write_pos;
          if (global_read_callback) {
            read_bytes += global_read_callback(dest_buffer + read_bytes, required_bytes - read_bytes);
          }
        } else {
          memcpy(dest_buffer + read_bytes, &context->data[0], required_bytes - read_bytes);
          context->read_pos = required_bytes - read_bytes;
          read_bytes = required_bytes;
        }
      } else {
        read_bytes = required_bytes;
        memcpy(dest_buffer, &context->data[context->read_pos], read_bytes);
        context->read_pos += read_bytes;
      }
  }

  return read_bytes;
}

uint32_t write_to_circular_buffer(void *buffer, uint32_t size, uint32_t count, void *handle_ptr)
{
  CircularBufferContext *context = (CircularBufferContext *)handle_ptr;
  uint8_t *src_buffer = (uint8_t *)buffer;
  uint32_t remaining_bytes;
  uint32_t bytes_to_write = size * count;
  uint32_t read_pos = context->read_pos;

  if (context->write_pos >= read_pos) {
    remaining_bytes = context->capacity - context->write_pos + read_pos;

    if (remaining_bytes >= bytes_to_write + SAFE_INTERVAL) {
      remaining_bytes = context->capacity - context->write_pos;

      if (remaining_bytes >= bytes_to_write) {
        memcpy(&context->data[context->write_pos], src_buffer, bytes_to_write);
        context->write_pos += bytes_to_write;
      } else {
        memcpy(&context->data[context->write_pos], src_buffer, remaining_bytes);
        context->write_pos = bytes_to_write - remaining_bytes;
        memcpy(&context->data[0], &src_buffer[remaining_bytes], context->write_pos);
      }
    } else {
      return 0;
    }
  } else {
    remaining_bytes = read_pos - context->write_pos;

    if (remaining_bytes >= bytes_to_write + SAFE_INTERVAL) {
      memcpy(&context->data[context->write_pos], src_buffer, bytes_to_write);
      context->write_pos += bytes_to_write;
    } else {
      return 0;
    }
  }

  if (context->write_pos >= context->capacity && context->read_pos) {
    context->write_pos = 0;
  }

  return bytes_to_write;
}

int32_t circular_buffer_used_space(void *handle)
{
  CircularBufferContext *context = (CircularBufferContext *)handle;
  return context->write_pos >= context->read_pos ?
          context->write_pos - context->read_pos :
          context->capacity - context->read_pos + context->write_pos;
}

int32_t circular_buffer_available_space(void *handle)
{
  int32_t available = ((CircularBufferContext *)handle)->capacity - circular_buffer_used_space(handle);
  return available > SAFE_INTERVAL ? available - SAFE_INTERVAL : 0;
}

void reset_circular_buffer(void *handle)
{
    CircularBufferContext *context = (CircularBufferContext *)handle;
    context->read_pos = 0;
    context->write_pos = 0;
}